﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using Orchard.ContentManagement;
using Orchard.ContentManagement.Definition.Models;
using Orchard.ContentManagement.Parts;
using Orchard.Core.Common.Fields;
using Orchard.Localization;
using Orchard.ContentManagement.FieldStorage;
using Orchard.Security;

namespace Orchard.Tokens.Providers
{
    public class ContentTokens : ITokenProvider
    {
        private readonly IContentManager _contentManager;

        public ContentTokens(IContentManager contentManager) {
            _contentManager = contentManager;
            T = NullLocalizer.Instance;
        }

        public Localizer T { get; set; }

        public void Describe(DescribeContext context)
        {
            context.For("Content", T("Content Items"), T("Content Items"))
                .Token("Id", T("Content Id"), T("Numeric primary key value of content."))
                .Token("Author", T("Content Author"), T("Person in charge of the content."), "User")
                .Token("Date", T("Content Date"), T("Date the content was created."), "DateTime")
                .Token("Identity", T("Identity"), T("Identity of the content."))
                .Token("ContentType", T("Content Type"), T("The name of the item Content Type."), "TypeDefinition");

            context.For("TextField", T("Text Field"), T("Tokens for Text Fields"))
                .Token("Length", T("Length"), T("The length of the field."));

            context.For("TypeDefinition", T("Type Definition"), T("Tokens for Content Types"))
                .Token("Name", T("Name"), T("Name of the content type."))
                .Token("DisplayName", T("Display Name"), T("Display name of the content type."))
                .Token("Parts", T("Parts"), T("List of the attached part names."))
                .Token("Fields", T("Fields"), T("Fields for each of the attached parts. For example, Fields.Page.Approved."));
        }

        public void Evaluate(EvaluateContext context)
        {
            context.For<IContent>("Content")
                .Token("Id", content => content.Id)
                .Token("Author", AuthorName)
                .Chain("Author", "User", content => content.As<ICommonPart>().Owner)
                .Token("Date", content => content.As<ICommonPart>().CreatedUtc)
                .Chain("Date", "DateTime", content => content.As<ICommonPart>().CreatedUtc)
                .Token("Identity", content => _contentManager.GetItemMetadata(content).Identity.ToString())
                .Token("ContentType", content => content.ContentItem.TypeDefinition.DisplayName)
                .Chain("ContentType", "TypeDefinition", content => content.ContentItem.TypeDefinition);

            if (context.Target == "Content")
            {
                var forContent = context.For<IContent>("Content");
                foreach (var typePart in forContent.Data.ContentItem.TypeDefinition.Parts)
                {
                    var part = typePart;
                    foreach (var partField in typePart.PartDefinition.Fields)
                    {
                        var field = partField;
                        var tokenName = "Fields." + typePart.PartDefinition.Name + "." + partField.Name;
                        forContent.Token(
                            tokenName,
                            content => LookupField(content, part.PartDefinition.Name, field.Name).Storage.Get<string>());
                        forContent.Chain(
                            tokenName,
                            partField.FieldDefinition.Name,
                            content => LookupField(content, part.PartDefinition.Name, field.Name));
                    }
                }
            }

            context.For<TextField>("TextField")
                .Token("Length", field => (field.Value ?? "").Length);

            context.For<ContentTypeDefinition>("TypeDefinition")
                .Token("Name", def => def.Name)
                .Token("DisplayName", def => def.DisplayName)
                .Token("Parts", def => string.Join(", ", def.Parts.Select(x => x.PartDefinition.Name).ToArray()))
                .Token("Fields", def => string.Join(", ", def.Parts.SelectMany(x => x.PartDefinition.Fields.Select(x2 => x2.FieldDefinition.Name + " " + x.PartDefinition.Name + "." + x2.Name)).ToArray()));
        }

        private IHtmlString AuthorName(IContent content)
        {
            var author = content.As<ICommonPart>().Owner;
            // todo: encoding should be done at a higher level automatically and should be configurable via an options param
            // so it can be disabled
            return author == null ? (IHtmlString)T("Anonymous") : new HtmlString(HttpUtility.HtmlEncode(author.UserName));
        }

        private static ContentField LookupField(IContent content, string partName, string fieldName)
        {
            return content.ContentItem.Parts
                .Where(part => part.PartDefinition.Name == partName)
                .SelectMany(part => part.Fields.Where(field => field.Name == fieldName))
                .SingleOrDefault();
        }
    }
}